package com.company.domain.repository;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.persistence.Query;

import org.apache.commons.lang3.StringUtils;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
import org.springframework.data.jpa.repository.support.PersistenceProvider;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.data.repository.NoRepositoryBean;

@NoRepositoryBean
public class BaseRepositoryImpl<T, ID extends Serializable> extends
		SimpleJpaRepository<T, ID> implements BaseRepository<T, ID> {

	@SuppressWarnings("unused")
	private final JpaEntityInformation<T, ?> entityInformation;
	private final EntityManager em;
	@SuppressWarnings("unused")
	private final PersistenceProvider provider;

	private Class<?> springDataRepositoryInterface;

	public Class<?> getSpringDataRepositoryInterface() {
		return springDataRepositoryInterface;
	}

	public void setSpringDataRepositoryInterface(
			Class<?> springDataRepositoryInterface) {
		this.springDataRepositoryInterface = springDataRepositoryInterface;
	}

	public BaseRepositoryImpl(Class<T> domainClass, EntityManager em) {
		this(JpaEntityInformationSupport.getMetadata(domainClass, em), em, null);
	}

	public BaseRepositoryImpl(JpaEntityInformation<T, ?> entityInformation,
			EntityManager entityManager, Class<?> springDataRepositoryInterface) {
		super(entityInformation, entityManager);
		this.entityInformation = entityInformation;
		this.em = entityManager;
		this.provider = PersistenceProvider.fromEntityManager(entityManager);
		this.springDataRepositoryInterface = springDataRepositoryInterface;
	}

	public T findSingleObject(String queryString, Object... params) {
		try {
			List<T> list = executeQuery(queryString, null, null, params);
			if (list.size() != 1 || list.get(0) == null) {
				throw new Exception("Multiply or no entries.");
			}
			return list.get(0);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	@SuppressWarnings("unchecked")
	public List<Map<String, Object>> executeNativeQuery(String queryString,
			Integer startPosition, Integer maxResult, String[] resultFields,
			Object... params) {
		Query query = this.em.createNativeQuery(queryString);
		if (params != null) {
			for (int i = 0; i < params.length; i++) {
				query.setParameter(i + 1, params[i]);
			}
		}
		if (startPosition != null) {
			query.setFirstResult(startPosition);
		}
		if (maxResult != null) {
			query.setMaxResults(maxResult);
		}
		try {
			List<Object[]> list = query.getResultList();
			List<Map<String, Object>> returnList = new ArrayList<Map<String, Object>>();
			for (Object[] obj : list) {
				Map<String, Object> map = new HashMap<String, Object>();
				int i = 0;
				for (String field : resultFields) {
					map.put(field, obj[i++]);
				}
				returnList.add(map);
			}
			return returnList;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	public int executeNativeQuery(String queryString, Object... params) {
		Query query = this.em.createNativeQuery(queryString);
		if (params != null) {
			for (int i = 0; i < params.length; i++) {
				query.setParameter(i + 1, params[i]);
			}
		}
		try {
			return query.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return -1;
	}

	@SuppressWarnings("unchecked")
	public List<T> executeQuery(String queryString, Integer startPosition,
			Integer maxResult, Object... params) {
		Query query = this.em.createQuery(queryString);
		query.setHint("org.hibernate.cacheable", true);
		if (params != null) {
			for (int i = 0; i < params.length; i++) {
				query.setParameter(i + 1, params[i]);
			}
		}
		if (startPosition != null) {
			query.setFirstResult(startPosition);
		}
		if (maxResult != null) {
			query.setMaxResults(maxResult);
		}
		try {
			return ((List<T>) query.getResultList());
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	@SuppressWarnings("unchecked")
	public List<T> executeListQuery(String queryString, Integer startPosition,
			Integer maxResult, Map<String, Object> params) {
		Query query = this.em.createQuery(queryString);
		query.setHint("org.hibernate.cacheable", true);
		if (params != null) {
			Set<String> keyParams = params.keySet();
			for (String key : keyParams) {
				query.setParameter(key, params.get(key));
			}
		}
		if (startPosition != null) {
			query.setFirstResult(startPosition);
		}
		if (maxResult != null) {
			query.setMaxResults(maxResult);
		}
		try {
			return ((List<T>) query.getResultList());
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	public Long executeCountQuery(String queryString, Object... params) {
		Query query = this.em.createQuery(queryString);
		query.setHint("org.hibernate.cacheable", true);
		if (params != null) {
			for (int i = 0; i < params.length; i++) {
				query.setParameter(i + 1, params[i]);
			}
		}
		try {
			return (Long) query.getSingleResult();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	public Long executeCountQuery(String queryString, Integer startPosition,
			Integer maxResult, Map<String, Object> params) {
		Query query = this.em.createQuery(queryString);
		query.setHint("org.hibernate.cacheable", true);
		if (params != null) {
			Set<String> keyParams = params.keySet();
			for (String key : keyParams) {
				query.setParameter(key, params.get(key));
			}
		}
		if (startPosition != null) {
			query.setFirstResult(startPosition);
		}
		if (maxResult != null) {
			query.setMaxResults(maxResult);
		}
		try {
			return (Long) query.getSingleResult();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	public Map<String, Object> createQueryString(String entityName,
			String propertyName, Map<String, Object> attr) {

		return createQueryString(entityName, propertyName, attr, null);
	}

	public Map<String, Object> createQueryString(String entityName,
			String propertyName, Map<String, Object> attr,
			Map<String, String> operators) {

		String queryString = "SELECT x";

		if (!StringUtils.isEmpty(propertyName)) {
			queryString += "." + propertyName;
		}

		if (!StringUtils.isEmpty(propertyName)
				&& propertyName.contains("count")) {
			queryString = "SELECT count(x)";
		}

		queryString += " FROM " + entityName + " x";
		String whereString = "";
		Object[] params = null;
		if (attr != null && !attr.isEmpty()) {
			List<Object> paramsList = new ArrayList<Object>();
			Set<String> keys = attr.keySet();
			out: for (String key : keys) {

				Object obj = attr.get(key);

				if (obj == null) {
					continue out;
				}

				if (obj instanceof String) {
					if (StringUtils.isEmpty(obj.toString())
							|| obj.toString().contains("null")) {
						continue out;
					}
				}

				if (!"".equals(whereString)) {
					whereString = whereString + " AND";
				}

				if (operators == null || operators.isEmpty()) {
					whereString = whereString + " x." + key + " = ?";
				}

				String operator = null;
				
				if (operators != null && !operators.isEmpty()) {
					operator = operators.get(key);

					if (StringUtils.isEmpty(operator)) {
						whereString = whereString + " x." + key + " = ?";
					}

					if (!StringUtils.isEmpty(operator)) {
						whereString = whereString + " x." + key + " "
								+ operator + " ?";
					}

				}

				if (!StringUtils.isEmpty(operator) && "like".equals(operator)) {
					obj = "%"+obj+"%";					
				}
				
				paramsList.add(obj);
			}
			params = new Object[paramsList.size()];
			params = paramsList.toArray(params);
		}
		if (!"".equals(whereString)) {
			queryString = queryString + " WHERE" + whereString;
		}

		Map<String, Object> result = new HashMap<String, Object>();
		result.put("queryString", queryString);
		result.put("params", params);
		return result;
	}

	@SuppressWarnings("unchecked")
	public Map<String, Object> createParameterizedQueryString(
			String entityName, String propertyName, Map<String, Object> attr) {
		String queryString = "SELECT x"
				+ ((propertyName != null && !"".equals(propertyName)) ? "."
						+ propertyName : "") + " FROM " + entityName + " x";
		String whereString = "";
		Map<String, Object> params = null;
		if (attr != null && !attr.isEmpty()) {
			params = new HashMap<String, Object>();
			for (String key : attr.keySet()) {
				if (!"".equals(whereString)) {
					whereString = whereString + " AND";
				}

				if (attr.get(key) instanceof Map) {
					Map<String, Object> range = (Map<String, Object>) attr
							.get(key);
					Object[] keys = range.keySet().toArray();
					if (keys.length != 2) {
						continue;
					}

					String fromKey = (String) keys[0];
					String thruKey = (String) keys[1];
					if (!fromKey.startsWith("from")
							|| !thruKey.startsWith("thru")) {
						fromKey = (String) keys[1];
						thruKey = (String) keys[0];
					}

					if (range.get(thruKey) == null) {
						whereString = whereString + " x." + key + " >= :"
								+ fromKey;
						params.put(fromKey, range.get(fromKey));
					} else if (range.get(fromKey) == null) {
						whereString = whereString + " x." + key + " <= :"
								+ thruKey;
						params.put(thruKey, range.get(thruKey));
					} else {
						whereString = whereString + " x." + key + " BETWEEN :"
								+ fromKey + " AND :" + thruKey;
						params.put(fromKey, range.get(fromKey));
						params.put(thruKey, range.get(thruKey));
					}

				} else {
					whereString = whereString + " x." + key + " = :" + key;
					params.put(key, attr.get(key));
				}
			}
		}
		if (!"".equals(whereString)) {
			queryString = queryString + " WHERE" + whereString;
		}

		Map<String, Object> result = new HashMap<String, Object>();
		result.put("queryString", queryString);
		result.put("params", params);
		return result;
	}

}